// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugin.common;

import android.support.annotation.Nullable;
import java.util.Map;
import org.json.JSONObject;

import io.flutter.BuildConfig;

/**
 * Command object representing a method call on a {@link MethodChannel}.
 */
public final class MethodCall {
    /**
     * The name of the called method.
     */
    public final String method;

    /**
     * Arguments for the call.
     *
     * <p>Consider using {@link #arguments()} for cases where a particular run-time type is expected.
     * Consider using {@link #argument(String)} when that run-time type is {@link Map} or
     * {@link JSONObject}.</p>
     */
    public final Object arguments;

    /**
     * Creates a {@link MethodCall} with the specified method name and arguments.
     *
     * @param method the method name String, not null.
     * @param arguments the arguments, a value supported by the channel's message codec.
     */
    public MethodCall(String method, Object arguments) {
        if (BuildConfig.DEBUG && method == null) {
            throw new AssertionError("Parameter method must not be null.");
        }
        this.method = method;
        this.arguments = arguments;
    }

    /**
     * Returns the arguments of this method call with a static type determined by the call-site.
     *
     * @param <T> the intended type of the arguments.
     * @return the arguments with static type T
     */
    @SuppressWarnings("unchecked")
    public <T> T arguments() {
        return (T) arguments;
    }

    /**
     * Returns a String-keyed argument of this method call, assuming {@link #arguments} is a
     * {@link Map} or a {@link JSONObject}. The static type of the returned result is determined
     * by the call-site.
     *
     * @param <T> the intended type of the argument.
     * @param key the String key.
     * @return the argument value at the specified key, with static type T, or {@code null}, if
     * such an entry is not present.
     * @throws ClassCastException if {@link #arguments} can be cast to neither {@link Map} nor
     * {@link JSONObject}.
     */
    @SuppressWarnings("unchecked")
    @Nullable
    public <T> T argument(String key) {
        if (arguments == null) {
            return null;
        } else if (arguments instanceof Map) {
            return (T) ((Map<?, ?>) arguments).get(key);
        } else if (arguments instanceof JSONObject) {
            return (T) ((JSONObject) arguments).opt(key);
        } else {
            throw new ClassCastException();
        }
    }

    /**
     * Returns whether this method call involves a mapping for the given argument key,
     * assuming {@link #arguments} is a {@link Map} or a {@link JSONObject}. The value associated
     * with the key, as returned by {@link #argument(String)}, is not considered, and may be
     * {@code null}.
     *
     * @param key the String key.
     * @return {@code true}, if {@link #arguments} is a {@link Map} containing key, or a
     * {@link JSONObject} with a mapping for key.
     * @throws ClassCastException if {@link #arguments} can be cast to neither {@link Map} nor
     * {@link JSONObject}.
     */
    public boolean hasArgument(String key) {
        if (arguments == null) {
            return false;
        } else if (arguments instanceof Map) {
            return ((Map<?, ?>) arguments).containsKey(key);
        } else if (arguments instanceof JSONObject) {
            return ((JSONObject) arguments).has(key);
        } else {
            throw new ClassCastException();
        }
    }
}
